package com.scarecrow.olga.core.controller {
	
	import com.scarecrow.olga.error.VerboseError;
	import com.scarecrow.olga.task.event.TaskEvent;
	import com.scarecrow.olga.task.Task;
	import com.scarecrow.olga.util.ASSERT;
	import com.scarecrow.olga.util.logging.Logger;
	import flash.events.EventDispatcher;
	import flash.utils.Dictionary;
	import flash.utils.getQualifiedClassName;
	
	/**
	 * ...
	 * @author Santiago.J.Elizalde
	 */
	public class TaskController {
		
		private static var _strictMode:Boolean;
		private static var eventBroadcaster:EventDispatcher;
		
		protected static var activeTasks:Dictionary;
		protected static var pausedTasks:Dictionary;
		
		public function TaskController() {
			
		}
		
		/**
		 * Adds a task to the controllers active list and starts listening to its events. Once a task is
		 * registered, it will be managed by the controller.
		 * 
		 * All tasks will register THEMSELVES when their execute() method is called.
		 * 
		 * @param	task the Task being registered.
		 * @throws	CONTROL_ERROR if the task being registered shares type with an already active task.
		 */
		public static function registerTask(task:Task):void {
			
			if(!activeTasks) activeTasks = new Dictionary();
			if(!pausedTasks) pausedTasks = new Dictionary();
			if (!eventBroadcaster) eventBroadcaster = new EventDispatcher();
			
			if (activeTasks[task.type]) {
				throw new VerboseError("Control Error", "Attempted to execute a task while another one of the same type was active.", { taskType:task.type, taskClass:getQualifiedClassName(task)} );
			}
			
			activeTasks[task.type] = task;
			task.addEventListener(TaskEvent.TASK_COMPLETE, onTaskComplete);
			task.addEventListener(TaskEvent.TASK_CANCELLED, onTaskCancelled);
			task.addEventListener(TaskEvent.TASK_PAUSED, onTaskPaused);
			task.addEventListener(TaskEvent.TASK_FAILURE, onTaskFailure);
		}
		
		// ----- TaskEvent handlers --------------------------------------------------------------------------
		
		/**
		 * Handles a task being completed. This will remove the task from the controller and re-trigger 
		 * the event from the controller's own event broadcaster.
		 * 
		 * @param	e
		 */
		private static function onTaskComplete(e:TaskEvent):void {
			
			delete activeTasks[e.task.type];
			
			e.task.removeEventListener(TaskEvent.TASK_COMPLETE, onTaskComplete);
			e.task.removeEventListener(TaskEvent.TASK_CANCELLED, onTaskCancelled);
			e.task.removeEventListener(TaskEvent.TASK_PAUSED, onTaskPaused);
			e.task.removeEventListener(TaskEvent.TASK_FAILURE, onTaskFailure);
			
			Logger.log("Task '" + e.task.toString() + "' (" + e.task.type + ") was completed.", Logger.LOG_LEVEL_DEBUG);
			eventBroadcaster.dispatchEvent(e);
		}
		
		/**
		 * Handles a failed task. This will remove the task from the controller and re-trigger the event
		 * from the controller's own broadcaster. If set to strict mode, the controller will throw an 
		 * error instead of re-dispatching the event.
		 * 
		 * @param	e
		 */
		private static function onTaskFailure(e:TaskEvent):void {
			
			delete activeTasks[e.task.type];
			
			e.task.removeEventListener(TaskEvent.TASK_COMPLETE, onTaskComplete);
			e.task.removeEventListener(TaskEvent.TASK_CANCELLED, onTaskCancelled);
			e.task.removeEventListener(TaskEvent.TASK_PAUSED, onTaskPaused);
			e.task.removeEventListener(TaskEvent.TASK_FAILURE, onTaskFailure);
			
			if(_strictMode) {
				throw new VerboseError("Task has failed to complete.", e.eventMessage, { taskType:e.taskType });
			} else {
				Logger.log("Task '" + e.task.toString() + "' (" + e.task.type + ") has failed to complete." + "\n" + " - reason: " + e.eventMessage, Logger.LOG_LEVEL_ERROR);
				eventBroadcaster.dispatchEvent(e);
			}
		}
		
		/**
		 * Handles a task being paused. This will remove the task from the controller's active tasks and 
		 * re-trigger the event from the controller's own broadcaster.
		 * 
		 * @param	e
		 */
		private static function onTaskPaused(e:TaskEvent):void {
			
			pausedTasks[e.task] = e.task.type;
			delete activeTasks[e.task.type];
			
			e.task.removeEventListener(TaskEvent.TASK_COMPLETE, onTaskComplete);
			e.task.removeEventListener(TaskEvent.TASK_CANCELLED, onTaskCancelled);
			e.task.removeEventListener(TaskEvent.TASK_PAUSED, onTaskPaused);
			e.task.removeEventListener(TaskEvent.TASK_FAILURE, onTaskFailure);
			e.task.addEventListener(TaskEvent.TASK_RESUMED, onTaskResumed);
			
			Logger.log("Task '" + e.task.toString() + "' (" + e.task.type + ") was paused.", Logger.LOG_LEVEL_DEBUG);
			eventBroadcaster.dispatchEvent(e);
		}
		
		/**
		 * Handles a task being resumed. This will add the task to the controller's active tasks and 
		 * re-trigger the event from the controller's own broadcaster.
		 * 
		 * @param	e
		 */
		private static function onTaskResumed(e:TaskEvent):void {
			
			if (activeTasks[e.task.type]) {
				if (activeTasks[e.task.type] == e.task) {
					throw new VerboseError("Control Error", "Attempted to resume a task that was not paused.", { taskType:e.task.type, taskClass:e.task.toString()} );
				} else {
					throw new VerboseError("Control Error", "Attempted to resume a paused task while another one of the same type was active.", { taskType:e.task.type, taskClass:e.task.toString()} );
				}
			}
			activeTasks[e.task.type] = e.task;
			delete pausedTasks[e.task];
			
			e.task.addEventListener(TaskEvent.TASK_COMPLETE, onTaskComplete);
			e.task.addEventListener(TaskEvent.TASK_CANCELLED, onTaskCancelled);
			e.task.addEventListener(TaskEvent.TASK_PAUSED, onTaskPaused);
			e.task.addEventListener(TaskEvent.TASK_FAILURE, onTaskFailure);
			e.task.removeEventListener(TaskEvent.TASK_RESUMED, onTaskResumed);
			
			Logger.log("Task '" + e.task.toString() + "' (" + e.task.type + ") was resumed.", Logger.LOG_LEVEL_DEBUG);
			eventBroadcaster.dispatchEvent(e);
		}
		
		/**
		 * Handles a task being cancelled. This will remove the task from the controller's active tasks
		 * and re-trigger the event from the controller's own broadcaster.
		 * 
		 * @param	e
		 */
		private static function onTaskCancelled(e:TaskEvent):void {
			
			delete activeTasks[e.task.type];
			
			e.task.removeEventListener(TaskEvent.TASK_COMPLETE, onTaskComplete);
			e.task.removeEventListener(TaskEvent.TASK_CANCELLED, onTaskCancelled);
			e.task.removeEventListener(TaskEvent.TASK_PAUSED, onTaskPaused);
			e.task.removeEventListener(TaskEvent.TASK_FAILURE, onTaskFailure);
			
			Logger.log("Task '" + e.task.toString() + "' (" + e.task.type + ") was cancelled." + "\n" + " - reason: " + e.eventMessage, Logger.LOG_LEVEL_DEBUG);
			eventBroadcaster.dispatchEvent(e);
		}
		
		/**
		 * strict mode indicates whether or not the controller throws an error on task failures.
		 */
		public static function get strictMode():Boolean {
			return _strictMode;
		}
		public static function set strictMode(value:Boolean):void {
			_strictMode = value;
		}
		
		public static function addEventListener(eventType:String, handler:Function):void {
			eventBroadcaster.addEventListener(eventType, handler, false, 0, true);
		}
		
		public static function removeEventListener(eventType:String, handler:Function):void {
			eventBroadcaster.removeEventListener(eventType, handler);
		}
	}
}